properties = ['object oriented', 'duck typed', 'productive', 'fun']

# do ... end forms a block it seems
# inside a block one defines variables used by `|`.
properties.each do
  |property|
  # Strings with format parameters use double quotes.
  puts "Ruby is #{property}."
end
# OR:
properties.each {|property| puts "Ruby is #{property}."}
# Probably `{` is the same message as `do`, or they are
# language built-ins.

# You can override variables:

# Strings that are literals use single quotes, avoiding any
# further evaluation.
language = 'Ruby'
puts "Hello #{language}!"
# => nil

language = 'fun Ruby'
puts "Hello #{language}!"
# => nil

# Everything is an expression in Ruby, it seems.

# This should not replace #{language} inside the printed
# string:
puts 'Hello #{language}!'

# Numbers are objects too:
4.class
# => Integer

# Inspection is easily available:
4.methods

# As in Smalltalk `filter` is called `select`:
4.methods.select do |method| method === :send end

# Apparently `:name` is some kind of keyword syntax or
# something.

# Methods are accessed via the `.` operator.

# Booleans are objects too, which is important for them, so
# that they can have useful methods. Just like in Smalltalk.

false.class
# => FalseClass
false.to_s
# => "false"

######################
# CONTROL STRUCTURES #
######################

# Some control structures (or should we say methods?):
x = 4
puts 'This appears to be false.' unless x == 4
# But how does it work?
puts 'This appears to be true.' if x == 4
# Precedence goes from right to left? Maybe it groups it
# like this:

# puts ('This appears to be true.' if x == 4)

# String 'This appears to be true.' gets send `if`, which
# returns something, that expects to receive a boolean. `x`
# gets send `==`, which results in something that expects
# another value to compare with `4`. `x == 4` results in a
# boolean.

# But how does it know, that it should not apply `puts`
# first?

# And how does it know, to not give only `x` to whatever
# string <- if returns?

# Maybe `unless` and `if` are actually operators and
# operators > function calls. And messages > operators.

# There seem to be unary messages and binary messages:

4.next
# => 5

4.==
# (irb):42:in `==': wrong number of arguments (given 0, expected 1) (ArgumentError)
#         from (irb):42:in `<main>'
#         from /gnu/store/lkq73l0ggrjx0sdldnqzz4cc2q0r1h5a-ruby-3.1.1/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
#         from /gnu/store/ad8zlycwj66y5npzi96p1rd4v7x9w3p7-profile/bin/irb:25:in `load'
#         from /gnu/store/ad8zlycwj66y5npzi96p1rd4v7x9w3p7-profile/bin/irb:25:in `<main>'

4.== 5
# => false

##############
# PRECEDENCE #
##############

# guess at precedence:
# 1. unary messages
# 2. binary messages
# 3. other messages
# 4. operators
# 5. procedure calls

# But is `puts` not also an object?
puts.class

=> NilClass

# -> But a string is not a
# recognized message like `==`!

unless 1 == 2
  puts "huhu"
else
  puts "nah"
end

# loops

x = x + 1 while x < 10
x = x - 1 until x == 1
while x < 10 do
  x = x + 1
  puts x
end
# But the block can be shortened:
while x < 10
  x = x + 1
  puts x
end

# Somehow the parser must recognize the different forms of
# `where`.

##############
# TRUTHINESS #
##############

puts 'something' if 1
puts 'something' if 0
puts 'something' if []
puts 'something' if ['abc']

# All of them are truthy. Maybe only false is falsy?

puts 'something' if false

# Apparently `nil` is also falsy.
 puts 'something' if nil

#####################
# LOGICAL OPERATORS #
#####################

true and true
true and false
false and true
false and false

true or true
true or false
false or true
false or false

##########################
# NON-SHORTCUT OPERATORS #
##########################

true & something_undefined
true & 1
1 & true  # a different error, as the message `&` on an `Integer` has a different implementation than `&` to TrueClass.
false | something_undefined
false | 2
# => true
# So `|` forces a boolean.
# Lets see what || does:
false || 2
# => 2
# That is useful.
2 | false  # error

###############
# TYPE SYSTEM #
###############

4 + 'one'  # does not work
4 + 4.0  # works, results in float

# Type checks happen at RUNTIME! (-> strongly, dynamically
# typed)

#############
# FUNCTIONS #
#############

# Types not checked at compile time -> Function gets
# accepted.
def add_them_up
  3 + 'zero'
end

add_them_up
# -> Error!

###############
# DUCK TYPING #
###############

i = 0
a = ['100', 100.0]
while i < a.length
  # array access via brackets
  # to_i to convert to integer
  # both types, Float and String accept a unary message called to_i.
  puts a[i].to_i
  i = i + 1
end
i

# So it is basically like both implement an interface by
# convention.

# "If it walks like a duck and quacks like a duck, it's a
# duck."
